package xyz.labmem.core.reflex

import cn.hutool.core.util.StrUtil
import cn.hutool.log.StaticLog
import it.sauronsoftware.junique.AlreadyLockedException
import it.sauronsoftware.junique.JUnique
import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty
import javafx.fxml.FXMLLoader
import javafx.scene.Scene
import javafx.scene.image.Image
import javafx.scene.layout.Pane
import javafx.stage.Stage
import kotlinx.coroutines.*
import xyz.labmem.core.annotation.FXController
import xyz.labmem.core.aspect.BuddyAdvice
import xyz.labmem.core.base.BaseController
import xyz.labmem.core.base.FXRedirectParam
import xyz.labmem.core.context.FXApplicationContext
import xyz.labmem.core.exception.LabFatalException
import xyz.labmem.core.util.getResourceFile
import xyz.labmem.core.util.gui.loading.LoadingAlert
import xyz.labmem.core.util.gui.notice.Notice
import kotlin.reflect.KClass


/**
 * 窗口构建注入
 * @Author lty
 * @Date 2021/9/15 15:21
 */
class FXBuilder {

    companion object {

        /**
         * 启动舞台
         */
        @DelicateCoroutinesApi
        fun <T : BaseController> startStage(target: Class<T>) = startStage(null, target)

        /**
         * 启动舞台
         */
        @DelicateCoroutinesApi
        fun <T : BaseController> startStage(target: KClass<T>) = startStage(null, target.java)

        /**
         * 启动舞台
         */
        @DelicateCoroutinesApi
        fun <T : BaseController> startStage(stage: Stage?, target: Class<T>) = startStage(stage, target, null)

        /**
         * 启动舞台
         */
        @JvmStatic
        @DelicateCoroutinesApi
        fun startStage(param: FXRedirectParam) {
            if (StrUtil.isNotBlank(param.toControlName)) {
                if (FXApplicationContext.controllerClazz.containsKey(param.toControlName))
                    startStage(null, FXApplicationContext.controllerClazz[param.toControlName]!!, param)
                else
                    StaticLog.error("未找到重定向controller:$param.toControlName")
            } else if (param.controlClazz != null) {
                startStage(null, param.controlClazz!!.java, param)
            }
        }

        /**
         * 启动舞台
         */
        @DelicateCoroutinesApi
        private fun <T : BaseController> startStage(stage: Stage?, target: Class<T>, param: FXRedirectParam?) {
            //controller注解
            val fxc = target.getAnnotation(FXController::class.java)
            fxc ?: throw LabFatalException("----<$target.name>窗口启动错误：找不到@FXController----")

            if (FXApplicationContext.onlyControllers.containsKey(target)) {
                val thisController = FXApplicationContext.onlyControllers[target] as T
                thisController.stage.hide()
                show(thisController, fxc, true)
                return
            }
            val baseStage = stage ?: Stage()

            //若展示前有加载框，先执行加载框
            if (StrUtil.isNotBlank(fxc.loadContrName)) {
                //增加目标controller参数
                var paramL = FXRedirectParam(target.simpleName)
                param?.let { paramL = it }
                loadingContr(fxc.loadContrName, fxc, paramL) {
                    instance(baseStage, target, fxc, param)
                }
            } else {
                val controller = instance(baseStage, target, fxc, param)
                if (fxc.main and FXApplicationContext.onlyApp) {
                    check(baseStage)
                }
                show(controller, fxc, false) {
                    FXApplicationContext.onlyControllers[target] = controller
                    baseStage.setOnCloseRequest {
                        FXApplicationContext.onlyControllers.remove(target)
                    }
                }
            }
        }

        /**
         * 窗口运行前运行加载窗口
         */
        @DelicateCoroutinesApi
        private fun <T : BaseController> loadingContr(
            loadName: String,
            fxcMAin: FXController,
            param: FXRedirectParam,
            initContrStage: (() -> T)
        ) {
            val target = FXApplicationContext.controllerClazz[loadName]
            if (target != null) {
                val baseStage = Stage()
                //controller注解
                val fxc = target.getAnnotation(FXController::class.java)
                fxc ?: throw LabFatalException("----<$target.name>加载窗口启动错误：找不到@FXController----")
                val controller = instance(baseStage, target, fxc, param)
                show(controller, fxc, false)
                //初始化目标Stage
                val simpleObjectProperty = SimpleObjectProperty<T>()
                Thread {
                    Platform.runLater {
                        simpleObjectProperty.value = initContrStage().apply {
                            stage.setOnShowing {
                                baseStage.close()
                            }
                        }
                    }
                }.start()
                simpleObjectProperty.addListener { _, _, v ->
                    GlobalScope.launch(Dispatchers.Main) {
                        withContext(Dispatchers.IO) {
                            if (fxcMAin.main and FXApplicationContext.onlyApp) {
                                check(baseStage)
                            }
                            try {
                                param.addParams("targetController", v)
                                target.getDeclaredMethod("loadingInit").apply {
                                    isAccessible = true
                                    invoke(controller)
                                }
                            } catch (e: Exception) {
                                StaticLog.warn("${target.simpleName}--加载窗口未重写【loadingInit】方法")
                            }
                        }
                        Platform.runLater {
                            //展示主页面
                            show(param.getParams()["targetController"] as BaseController, fxcMAin, false) {
                                FXApplicationContext.onlyControllers[target] = controller
                                baseStage.setOnCloseRequest {
                                    FXApplicationContext.onlyControllers.remove(target)
                                }
                            }
                        }
                    }
                }
            } else {
                StaticLog.error("未找到加载窗口：$loadName")
            }
        }

        /**
         * 公共初始化窗口Controller
         */
        @DelicateCoroutinesApi
        private fun <T : BaseController> instance(
            baseStage: Stage,
            target: Class<T>,
            fxc: FXController,
            param: FXRedirectParam?
        ): T {
            if (StrUtil.isNotBlank(fxc.title)) {
                baseStage.title = fxc.title
            }
            if (StrUtil.isNotBlank(fxc.icon)) {
                baseStage.icons.add(Image(getResourceFile(fxc.icon).stream))
            }
            val controller: T
            if (StrUtil.isNotBlank(fxc.fxmlPath)) {
                val loader = FXMLLoader(getResourceFile(fxc.fxmlPath).url).apply {
                    controller = BuddyAdvice.instance(target)
                    //重定向传值
                    if (param != null) {
                        controller.request = param
                    }
                    setController(controller)
                }
                controller.root = loader.load()
            } else {
                controller = BuddyAdvice.instance(target)   //使用代理创建对象
                controller.root = Pane()
                //重定向传值
                if (param != null) {
                    controller.request = param
                }
            }
            if (param != null && param.ownerRedirect && param.redirectController != null)
                baseStage.initOwner((param.redirectController as BaseController).stage)
            controller.stage = baseStage
            baseStage.scene = Scene(controller.root)
            //注入自己
            target.superclass.getDeclaredField("controllerSelf").apply {
                isAccessible = true
                set(controller, controller)
            }
            //注入工具
            controller.loadingAlert = LoadingAlert(controller.getWin())
            controller.notice = Notice(controller.getWin())

            return controller
        }

        /**
         * 检查程序是否已启动
         */
        private fun check(baseStage: Stage) {
            val appId = FXApplicationContext.mainControllerClazz?.name
            try {
                JUnique.acquireLock(appId)
            } catch (e: AlreadyLockedException) {
                Platform.runLater(baseStage::close)
                FXApplication.close()
                throw LabFatalException("程序已启动")
            }
        }

        @DelicateCoroutinesApi
        private fun show(contr: BaseController, fxc: FXController, hasStage: Boolean) {
            show(contr, fxc, hasStage, null)
        }

        @DelicateCoroutinesApi
        private fun show(contr: BaseController, fxc: FXController, hasStage: Boolean, only: (() -> Unit)?) {
            //窗口托盘
            if (fxc.tray) {
                contr.tray = fxc.myTray.java.getDeclaredConstructor().newInstance().apply {
                    listen(contr)
                }
            } else {
                if (fxc.only && only != null) {
                    only()
                }
                if (fxc.main) {
                    contr.stage.setOnHidden {
                        FXApplication.close()
                    }
                }
            }
            //初始化窗口
            if (!hasStage) {
                try {
                    contr::class.java.getDeclaredMethod("InitStage").apply {
                        isAccessible = true
                        invoke(contr)
                    }
                } catch (_: NoSuchMethodException) {
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            //show前执行
            try {
                contr::class.java.getDeclaredMethod("onShow").apply {
                    isAccessible = true
                    invoke(contr)
                }
            } catch (_: NoSuchMethodException) {
            } catch (e: Exception) {
                e.printStackTrace()
            }
            contr.stage.show()
        }

    }

}